﻿using System;
using System.Linq;

namespace OmniKits
{
    using NullGuard;

    public delegate void StacklessEventHandler<T>(object sender, StacklessEventArgs<T> e);
#pragma warning disable 1591

    public sealed class StacklessEventArgs<T> : EventArgs
    {
        private object _Sender;
        private StacklessEventHandler<T> _NextHandler;

        public T Value { get; set; }

        public StacklessEventArgs(T args = default(T))
        {
            this.Value = args;
        }

        private void CallNextHandlerInternal(object sender)
        {
            var handler = this._NextHandler;
            var last = (StacklessEventHandler<T>)handler.GetInvocationList().Last();
            this._NextHandler = handler - last;

            last(sender, this);

            if (this._NextHandler != null)
                this.CallNextHandlerInternal(sender);
        }

        public void InvokeEventHandler(object sender, [AllowNull]StacklessEventHandler<T> handler)
        {
            if (_NextHandler != null)
                throw new InvalidOperationException();

            if (handler == null) return;

            this._Sender = sender;
            this._NextHandler = handler;

            this.CallNextHandlerInternal(sender);

            this._Sender = null;
        }

        public static StacklessEventArgs<T> Invoke(object sender, StacklessEventHandler<T> handler, T args = default(T))
        {
            var sea = new StacklessEventArgs<T>(args);
            sea.InvokeEventHandler(sender, handler);
            return sea;
        }

        public bool Cancel()
        {
            if (this._NextHandler == null)
                return true;

            this._NextHandler = null;
            return false;
        }

        public void Invoke(object sender)
        {
            if (this._NextHandler == null)
                throw new InvalidOperationException();

            this.CallNextHandlerInternal(sender);
        }

        public void Invoke()
        {
            this.Invoke(this._Sender);
        }
    }
}
